3-5 动态健康检查:在.env中配置多微服务健康检查端点
问题:硬编码多微服务健康检查
上一节在 Gateway 中为每个微服务硬编码了健康检查配置。当微服务数量增加时,需要不断修改代码添加新的健康检查项。解决方案是通过环境变量动态配置。
.env 配置格式
在 .env 文件中定义多微服务健康检查端点:
# 格式:serviceKey,grpcUrl,packageName,protoName,serviceName
# 示例:
# serviceKey|grpcUrl|packageName|protoName|serviceName
HEALTH_CHECK_ENDPOINTS=user-grpc|localhost:40001|user|user|user;user1-grpc|localhost:40002|user|user|user
bash
参数说明
| 参数位置 | 名称 | 说明 | 示例 |
|---|---|---|---|
| 1 | serviceKey | 唯一标识 | user-grpc |
| 2 | grpcUrl | gRPC 服务地址 | localhost:40001 |
| 3 | packageName | proto package 名 | user |
| 4 | protoName | proto 文件名 | user |
| 5 | serviceName | proto service 名(可选) | user |
格式设计
使用 | 分隔不同的服务配置,使用 , 分隔同一服务内的参数:
服务1参数1,服务1参数2,服务1参数3|服务2参数1,服务2参数2,服务2参数3
text
动态 Controller 实现
读取环境变量
@Controller('health')
export class ConsulController {
constructor(
private health: HealthCheckService,
private grpc: GRPCHealthIndicator,
private configService: ConfigService,
) {}
@Get()
check(@Query('service') service: string) {
const endpoints = this.configService.get('HEALTH_CHECK_ENDPOINTS');
if (!endpoints) {
throw new HttpException('No endpoints configured', 500);
}
const arr = endpoints.split('|');
const endpointsObj = this.parseEndpoints(arr);
if (service && endpointsObj[service]) {
return this.health.check([endpointsObj[service]]);
}
throw new HttpException('Service not found', 404);
}
}
typescript
动态解析端点配置
private parseEndpoints(arr: string[]) {
const endpointsObj: Record<string, () => Promise<any>> = {};
for (const item of arr) {
const [serviceKey, grpcUrl, packageName, protoName, serviceName] =
item.split(',');
endpointsObj[serviceKey] = () =>
this.grpc.checkHealth(serviceKey, {
url: grpcUrl,
package: packageName,
protoPath: loadProto(protoName || packageName),
timeout: 5000,
});
}
return endpointsObj;
}
typescript
手动管理 Response 状态码
Consul 的 HTTP 健康检查以 HTTP 状态码 为准(200 = 健康,非 200 = 不健康),而非响应体中的 status 字段。因此需要手动管理 Response 对象。
使用 @Res 装饰器
import { Controller, Get, Query, Res, HttpException } from '@nestjs/common';
import { Response } from 'express';
@Controller('health')
export class ConsulController {
@Get()
async check(
@Query('service') service: string,
@Res() res: Response,
) {
const endpoints = this.configService.get('HEALTH_CHECK_ENDPOINTS');
if (!endpoints) {
return res.status(500).json({ message: 'server error' });
}
const arr = endpoints.split('|');
const endpointsObj = this.parseEndpoints(arr);
if (service && endpointsObj[service]) {
try {
const result = await this.health.check([endpointsObj[service]]);
return res.status(200).json(result);
} catch (error) {
return res.status(500).json({ message: 'server error' });
}
}
return res.status(404).json({ message: 'service not found' });
}
}
typescript
状态码对应关系
| 场景 | HTTP 状态码 | Consul 行为 |
|---|---|---|
| 服务健康 | 200 | 标记为 passing |
| 服务不存在 | 404 | 标记为 critical |
| 检查出错 | 500 | 标记为 critical |
微服务 Consul 注册更新
每个微服务注册到 Consul 时,将健康检查地址指向 Gateway 的动态端点:
// user 微服务(40001)
check: {
http: 'http://gateway:3030/health?service=user-grpc',
interval: '10s',
}
// user1 微服务(40002)
check: {
http: 'http://gateway:3030/health?service=user1-grpc',
interval: '10s',
}
typescript
添加新微服务的流程
添加新微服务时只需修改 .env 文件,无需改动代码:
# 在 HEALTH_CHECK_ENDPOINTS 中追加新服务
HEALTH_CHECK_ENDPOINTS=user-grpc|localhost:40001|user|user|user;user1-grpc|localhost:40002|user|user|user;order-grpc|localhost:40003|order|order|order
bash
重启 Gateway 即可生效。
proto name 可选处理
当 protoName 与 packageName 相同时,可以省略 protoName:
const protoPath = loadProto(protoName || packageName);
typescript
这样配置时可以少传一个参数:
# protoName 省略
user-grpc|localhost:40001|user
bash
参考资源
- NestJS Config - 配置管理
- Consul Health Checks - 健康检查机制
- Express Response - Response 对象 API
↑